package org.setec;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.setec.config.*;
import org.setec.manager.stubview.StorageViewTransformer;
import org.setec.utils.Utils;
import org.setec.utils.io.Persistance;
import org.setec.utils.io.Stub;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class InstrumentMocker {

    private static InterpretableConfiguration configuration;

    public static InterpretableConfiguration getConfiguration() {
        return configuration;
    }

    public static void setConfiguration(InterpretableConfiguration interpretableConfiguration) {
        configuration = interpretableConfiguration;
    }

    public static void loadConfiguration(String path) {
        configuration = ConfigLoader.load(path);
    }

    public static void loadConfigurationAsStream(InputStream stream) {
        configuration = ConfigLoader.load(stream);
    }

    private static void doInvalidate(String className, String methodName, Object[] args) {
        System.out.print("\nTRAP: blockMethodExecution:" + className + ":" + methodName);
        List stubs = new StorageViewTransformer().getStorageStubs(getConfiguration().getStorage());
        Set invalidStubs = new HashSet();
        for (int i = 0; i < getConfiguration().getAspectList().size(); i++) {
            Aspect aspect = (Aspect) getConfiguration().getAspectList().get(i);
            if (className.equals(aspect.getClassName())) {
                for (int j = 0; j < aspect.getJoinPoints().size(); j++) {
                    JoinPoint joinPoint = (JoinPoint) aspect.getJoinPoints().get(j);

                    for (Iterator it = joinPoint.getInvalidateDetails().iterator(); it.hasNext();) {
                        InvalidateDetails invalidateDetails = (InvalidateDetails) it.next();
                        int keyIndex = Integer.parseInt(invalidateDetails.getKeyIndex());
                        int contextIndex = Integer.parseInt(invalidateDetails.getContextIndex());
                        try {
                            //current stub is stub which fits invalidate criteria?
                            String keyValue = BeanUtils.getProperty(args[keyIndex], invalidateDetails.getKeyProp());
                            if (!keyValue.equals(invalidateDetails.getWhen())) {
                                continue;
                            }

                            //find all stubs od same context
                            String contextValue = BeanUtils.getProperty(args[contextIndex], invalidateDetails.getContextProp());
                            for (Iterator jt = stubs.iterator(); jt.hasNext();) {
                                Stub evaluatedStub = (Stub) jt.next();
                                String evaluatedContextValue = BeanUtils.getProperty(evaluatedStub.getArgs()[contextIndex], invalidateDetails.getContextProp());
                                if (evaluatedContextValue != null && evaluatedContextValue.equals(contextValue)) {
                                    invalidStubs.add(evaluatedStub);
                                }
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        for (Iterator it = invalidStubs.iterator(); it.hasNext();) {
            Stub stub = (Stub) it.next();
            Persistance.removeStub(getConfiguration().getStorage(), stub);
        }

    }


    /**
     * Method is allowed to execute if mode is LEARN, PASS, AUTO(no stub file)
     */
    public static boolean blockMethodExecution(String className, String methodName, Object[] args) {

        System.out.print("\nTRAP: blockMethodExecution:" + className + ":" + methodName);

        doInvalidate(className, methodName, args);

        if (getConfiguration().isProcessRealMethod(className, methodName, args)) {
            return false;
        } else {
            if (!Persistance.persists(getConfiguration().getStorage(), className, methodName, args)) {
                throw new IllegalStateException("Baad state");
            }
            return true;
        }
    }

    /**
     * MOCK or AUTO(stub file)
     */
    public static Object getMethodResultSubstitution(String className, String methodName, Object[] args) {

        System.out.print("\nTRAP: getMethodResultSubstitution:" + className + ":" + methodName);

        if (!Persistance.persists(getConfiguration().getStorage(), className, methodName, args)) {
            throw new IllegalStateException("Baad state");
        }

        Stub resultStub = Persistance.recall(getConfiguration().getStorage(), className, methodName, args);
        if (resultStub != null) {
            return resultStub.getResult();
        }
        return null;
    }

    public static Object processMethodResult(Object methodResult, String className, String methodName, Object[] args) {
        System.out.print("\nTRAP: processMethodResult:" + className + ":" + methodName);

        Object result = methodResult;
        if (getConfiguration().isLearn(className, methodName, args)) {
            Persistance.learn(getConfiguration().getStorage(), new Stub(className, methodName, args, result));
        }
        if (getConfiguration().isMock(className, methodName, args)) {
            Stub resultStub = Persistance.recall(getConfiguration().getStorage(), className, methodName, args);
            if (resultStub != null) {
                result = resultStub.getResult();
            }
        }
        return result;
    }

    public static void instrumentClasses() throws Throwable {
        ClassPool classPool = ClassPool.getDefault();
        classPool.insertClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
        for (Iterator it = getConfiguration().getAspectList().iterator(); it.hasNext();) {
            Aspect aspect = (Aspect) it.next();
            CtClass ctClass = classPool.get(aspect.getClassName());
            for (Iterator jpit = aspect.getJoinPoints().iterator(); jpit.hasNext();) {
                JoinPoint joinPoint = (JoinPoint) jpit.next();
                CtClass[] argClasses = new CtClass[joinPoint.getArgClassNames().length];
                for (int i = 0; i < joinPoint.getArgClassNames().length; i++) {
                    argClasses[i] = classPool.get(joinPoint.getArgClassNames()[i]);
                }
                CtMethod ctMethod = ctClass.getDeclaredMethod(joinPoint.getMethodName(), argClasses);

                ctMethod.insertBefore("{ if( org.setec.InstrumentMocker.blockMethodExecution(\"" + aspect.getClassName() + "\"," +
                        "\"" + joinPoint.getMethodName() + "\",$args) ) return org.setec.InstrumentMocker.getMethodResultSubstitution(" +
                        "\"" + aspect.getClassName() + "\",\"" + joinPoint.getMethodName() + "\",$args); }");

                ctMethod.insertAfter(" { return (" + ctMethod.getReturnType().getName() + ")org.setec.InstrumentMocker.processMethodResult(" +
                        "$_,\"" + aspect.getClassName() + "\",\"" + joinPoint.getMethodName() + "\",$args); } ");
            }
            ctClass.toClass();
        }
    }

}
